/*
 *   Copyright 2012-present OSBI Ltd
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

// Packages
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import ReduxBlockUi from 'react-block-ui/lib/redux';
import uuid from 'uuid/v4';
import $ from 'jquery';
import { Grid, Row, Col } from 'react-flexbox-grid';
import {
  Button,
  ButtonGroup,
  Card,
  ContextMenu,
  InputGroup,
  Intent,
  Menu,
  MenuDivider,
  MenuItem,
  Position,
  Tooltip,
  Tree
} from '@blueprintjs/core';
import { Debounce } from 'react-throttle';

// Services
import { RepositoryService } from '../../../services';

// Actions
import { actionCreators } from '../../../actions';

// Layout
import Container from '../../Layout/Container';

// UI
import { BlockUi, ErrorMessage, Loading, WarningAlert } from '../../UI';

// Dialogs
import { ViewFileInfoDialog } from '../../Dialogs';
import AddFolderDialog from './AddFolderDialog';
import PermissionsDialog from './PermissionsDialog';
import MoveObjectDialog from './MoveObjectDialog';

// Utils
import { Saiku, Settings } from '../../../utils';
import {
  REQUEST_START,
  REQUEST_SUCCESS,
  REQUEST_FAILURE,
  FOLDER,
  FILE
} from '../../../utils/constants';

// Constants
const ERROR_MSG = 'Error fetching data from repository';

class QueryManager extends Component {
  _isMounted = false;

  state = {
    treeNodesData: [],
    treeNodesStaticData: [],
    selectedFolderPath: '',
    selectedItem: '',
    selectedItemPath: '',
    repositoryObjectsHeight: 350,
    isOpenAddFolderDialog: false,
    isOpenViewFileInfoDialog: false,
    isOpenPermissionsDialog: false,
    isOpenMoveObjectDialog: false,
    isOpenQuery: false,
    isOpenDeleteAlert: false,
    loading: true,
    error: false,
    errorMsg: ERROR_MSG
  };

  componentWillMount() {
    const height = $('body').height() / 2 + $('body').height() / 6;

    this.setState({ repositoryObjectsHeight: height });
  }

  componentDidMount() {
    this._isMounted = true;
    this.callApiGetRepositories();
  }

  componentWillUnmount() {
    this._isMounted = false;

    if (ContextMenu.isOpen()) {
      ContextMenu.hide();
    }

    RepositoryService.cancelRequest();
  }

  callApiGetRepositories = () => {
    this.setState({
      loading: true,
      error: false
    });

    RepositoryService.getRepositories()
      .then(res => {
        if (this._isMounted && res.status === 200) {
          const { data } = res;

          this.setState(
            {
              treeNodesData: this.deepCloneReplace(data, {
                repoObjects: 'childNodes',
                name: 'label'
              }),
              loading: false
            },
            () => {
              this.setState({ treeNodesStaticData: this.state.treeNodesData });
            }
          );
        } else {
          if (this._isMounted) {
            this.setState({
              loading: false,
              error: true
            });
          }
        }
      })
      .catch(error => {
        if (this._isMounted) {
          this.setState({
            loading: false,
            error: true
          });
        }
      });
  };

  deepCloneReplace(obj, replaceMap) {
    const clone = {};

    Object.keys(obj).forEach(key => {
      let newKey = key;

      if (key in replaceMap) {
        if (replaceMap.hasOwnProperty(key)) {
          newKey = replaceMap[key];
        }
      }

      clone[newKey] =
        typeof obj[key] === 'object'
          ? this.deepCloneReplace(obj[key], replaceMap)
          : obj[key];

      if (clone[key] === FOLDER) {
        clone['hasCaret'] = true;
        clone['icon'] = 'folder-close';
        clone['secondaryLabel'] = (
          <ButtonGroup minimal>
            <Tooltip content="Permissions" position={Position.TOP}>
              <Button icon="people" onClick={this.handlePermissionsDialog} />
            </Tooltip>
            <Tooltip content="Delete" position={Position.TOP}>
              <Button icon="trash" onClick={this.handleDeleteAlert} />
            </Tooltip>
          </ButtonGroup>
        );
      } else if (clone[key] === FILE) {
        clone['icon'] = 'document';
        clone['secondaryLabel'] = (
          <ButtonGroup minimal>
            <Tooltip content="View" position={Position.TOP}>
              <Button icon="eye-open" onClick={this.handleViewFileInfoDialog} />
            </Tooltip>
            <Tooltip content="Open" position={Position.TOP}>
              <Button icon="play" onClick={this.handleOpenQuery} />
            </Tooltip>
            <Tooltip content="Permissions" position={Position.TOP}>
              <Button icon="people" onClick={this.handlePermissionsDialog} />
            </Tooltip>
            <Tooltip content="Delete" position={Position.TOP}>
              <Button icon="trash" onClick={this.handleDeleteAlert} />
            </Tooltip>
          </ButtonGroup>
        );
      }
    });

    return Array.isArray(obj) && obj.length
      ? (clone.length = obj.length) && Array.from(clone)
      : Array.isArray(obj)
      ? Array.from(obj)
      : clone;
  }

  searchTreeNode = (nodeData, matchingLabel) => {
    if (
      nodeData.label.toLowerCase().indexOf(matchingLabel.toLowerCase()) !== -1
    ) {
      return nodeData;
    } else if (nodeData.childNodes !== null && nodeData.type === FOLDER) {
      let result = null;

      for (let i = 0; result === null && i < nodeData.childNodes.length; i++) {
        result = this.searchTreeNode(nodeData.childNodes[i], matchingLabel);
      }

      return result;
    }

    return null;
  };

  handleChangeSearch = event => {
    const { value } = event.target;
    const newTreeNodesData = this.state.treeNodesStaticData
      .map(nodeData => this.searchTreeNode(nodeData, value))
      .filter((nodeData, i, self) => nodeData && self.indexOf(nodeData) === i);

    this.setState({ treeNodesData: newTreeNodesData });
  };

  forEachNode(nodes, callback) {
    if (nodes == null) {
      return;
    }

    for (const node of nodes) {
      callback(node);
      this.forEachNode(node.childNodes, callback);
    }
  }

  handleFolderState(nodeData, isExpanded = false, iconName = 'folder-close') {
    const { path } = nodeData;

    nodeData.isExpanded = isExpanded;
    nodeData.icon = iconName;
    this.setState(this.state);
    this.setState({
      selectedFolderPath: path,
      selectedItemPath: `${path}/`
    });
  }

  openQuery() {
    const { openQuery, requestStart, history } = this.props;
    const { selectedItemPath } = this.state;
    const file = selectedItemPath;
    const params = Object.assign(
      {
        uuid: uuid().toUpperCase(),
        file,
        formatter: Settings.CELLSET_FORMATTER
      },
      Settings.PARAMS
    );

    requestStart(<BlockUi message="Opening query..." />);

    openQuery(params);

    history.push('/workspace');
  }

  handleNodeClick = (nodeData, nodePath, event) => {
    const originallySelected = nodeData.isSelected;
    const { icon, path } = nodeData || null;
    const isSecondaryLabel = $(event.target)
      .closest('.bp3-tree-node-secondary-label')
      .hasClass('bp3-tree-node-secondary-label');

    if (!event.shiftKey) {
      this.forEachNode(this.state.treeNodesData, n => (n.isSelected = false));
    }

    nodeData.isSelected =
      originallySelected === null ? true : !originallySelected;

    if (!isSecondaryLabel) {
      this.setState(this.state);
    }

    if (path) {
      this.setState({
        selectedItem: nodeData.label,
        selectedItemPath:
          icon === 'folder-close' || icon === 'folder-open' ? `${path}/` : path
      });
    }

    if (path && icon === 'document') {
      const [selectedFolderPath] = path.split(nodeData.label);

      this.setState({ selectedFolderPath, selectedItemPath: path }, () => {
        const { isOpenQuery } = this.state;

        if (isOpenQuery) {
          this.openQuery();
        }
      });
    } else if (!isSecondaryLabel) {
      if (!nodeData.isExpanded) {
        this.handleFolderState(nodeData, true, 'folder-open');
      } else {
        this.handleFolderState(nodeData);
      }
    }
  };

  handleNodeCollapse = nodeData => {
    this.handleFolderState(nodeData);
  };

  handleNodeContextMenu = (nodeData, nodePath, event) => {
    event.preventDefault();

    const { icon, path, type } = nodeData || null;

    if (path) {
      this.setState({
        selectedItem: nodeData.label,
        selectedItemPath:
          icon === 'folder-close' || icon === 'folder-open' ? `${path}/` : path
      });
    }

    if (path && icon === 'document') {
      const [selectedFolderPath] = path.split(nodeData.label);

      this.setState({ selectedFolderPath, selectedItemPath: path });
    } else {
      this.setState({
        selectedFolderPath: path,
        selectedItemPath: `${path}/`
      });
    }

    ContextMenu.show(
      <Menu>
        {type === FILE && (
          <Fragment>
            <MenuItem
              icon="eye-open"
              text="View"
              onClick={this.handleViewFileInfoDialog}
            />
            <MenuItem icon="play" text="Open" onClick={this.handleOpenQuery} />
          </Fragment>
        )}
        <MenuItem
          icon="people"
          text="Permissions"
          onClick={this.handlePermissionsDialog}
        />
        <MenuItem
          icon="move"
          text="Move"
          onClick={this.handleMoveObjectDialog}
        />
        <MenuItem icon="trash" text="Delete" onClick={this.handleDeleteAlert} />
        <MenuDivider />
        <MenuItem
          icon="folder-new"
          text="Add Folder"
          onClick={this.handleAddFolderDialog}
        />
      </Menu>,
      { left: event.clientX, top: event.clientY },
      () => {
        const { isOpenQuery } = this.state;

        if (isOpenQuery) {
          this.openQuery();
        }
      }
    );
  };

  handleNodeExpand = nodeData => {
    this.handleFolderState(nodeData, true, 'folder-open');
  };

  handleAddFolderDialog = () => {
    this.setState(prevState => ({
      isOpenAddFolderDialog: !prevState.isOpenAddFolderDialog
    }));
  };

  handleViewFileInfoDialog = () => {
    this.setState(prevState => ({
      isOpenViewFileInfoDialog: !prevState.isOpenViewFileInfoDialog
    }));
  };

  handlePermissionsDialog = () => {
    this.setState(prevState => ({
      isOpenPermissionsDialog: !prevState.isOpenPermissionsDialog
    }));
  };

  handleMoveObjectDialog = () => {
    this.setState(prevState => ({
      isOpenMoveObjectDialog: !prevState.isOpenMoveObjectDialog
    }));
  };

  handleOpenQuery = () => {
    this.setState(prevState => ({
      isOpenQuery: !prevState.isOpenQuery
    }));
  };

  handleDeleteAlert = () => {
    this.setState(prevState => ({
      isOpenDeleteAlert: !prevState.isOpenDeleteAlert
    }));
  };

  handleDeleteItem = () => {
    const { requestStart, requestSuccess, requestFailure } = this.props;
    const { selectedItem, selectedItemPath } = this.state;

    requestStart(<BlockUi message={`Deleting ${selectedItem}...`} />);

    RepositoryService.deleteObject(selectedItemPath)
      .then(res => {
        if (res.status === 200) {
          requestSuccess();

          this.setState({ selectedItemPath: '' });

          Saiku.toasts(Position.TOP_RIGHT).show({
            icon: 'tick',
            intent: Intent.SUCCESS,
            message: 'Deleted successfully'
          });

          this.callApiGetRepositories();
        } else {
          requestFailure();
          Saiku.toasts(Position.TOP_RIGHT).show({
            icon: 'error',
            intent: Intent.DANGER,
            message: 'Could not delete repository object'
          });
        }
      })
      .catch(error => {
        requestFailure();
        Saiku.toasts(Position.TOP_RIGHT).show({
          icon: 'error',
          intent: Intent.DANGER,
          message: 'Something went wrong'
        });
      });
  };

  renderTree() {
    const {
      treeNodesData,
      selectedItemPath,
      repositoryObjectsHeight
    } = this.state;

    return (
      <Grid fluid>
        <Row>
          <Col xs={10}>
            <Debounce time="100" handler="onChange">
              <InputGroup
                leftIcon="search"
                placeholder="Search..."
                onChange={this.handleChangeSearch}
              />
            </Debounce>
          </Col>
          <Col xs={2}>
            <Button
              icon="folder-new"
              text="Add Folder"
              intent={Intent.DANGER}
              onClick={this.handleAddFolderDialog}
              fill
            />
          </Col>
        </Row>
        <Row className="m-t-10">
          <Col xs>
            <Card
              style={{ height: repositoryObjectsHeight, overflowY: 'auto' }}
            >
              <Tree
                contents={treeNodesData}
                onNodeClick={this.handleNodeClick}
                onNodeCollapse={this.handleNodeCollapse}
                onNodeContextMenu={this.handleNodeContextMenu}
                onNodeExpand={this.handleNodeExpand}
              />
            </Card>
          </Col>
        </Row>
        <Row className="m-t-10">
          <Col xs>
            <b>{selectedItemPath}</b>
          </Col>
        </Row>
      </Grid>
    );
  }

  render() {
    const { blockUi } = this.props;
    const {
      selectedFolderPath,
      selectedItem,
      selectedItemPath,
      isOpenAddFolderDialog,
      isOpenViewFileInfoDialog,
      isOpenPermissionsDialog,
      isOpenMoveObjectDialog,
      isOpenDeleteAlert,
      loading,
      error,
      errorMsg
    } = this.state;

    return (
      <Container>
        <ReduxBlockUi
          tag="div"
          className="content-inner"
          block={REQUEST_START}
          unblock={[REQUEST_SUCCESS, REQUEST_FAILURE]}
          message={blockUi.message}
          loader={() => false}
          keepInView
        >
          <div className="sku-settings-content">
            {loading ? (
              <Loading className="m-t-20" size={30} center />
            ) : error ? (
              <ErrorMessage
                text={errorMsg}
                callApi={this.callApiGetRepositories}
              />
            ) : (
              this.renderTree()
            )}
          </div>

          {isOpenAddFolderDialog && (
            <AddFolderDialog
              folderPath={selectedFolderPath}
              getRepositories={this.callApiGetRepositories}
              onClose={this.handleAddFolderDialog}
            />
          )}

          {isOpenViewFileInfoDialog && (
            <ViewFileInfoDialog
              file={selectedItemPath}
              fileName={selectedItem}
              type="saiku"
              onClose={this.handleViewFileInfoDialog}
            />
          )}

          {isOpenPermissionsDialog && (
            <PermissionsDialog
              itemPath={selectedItemPath}
              onClose={this.handlePermissionsDialog}
            />
          )}

          {isOpenMoveObjectDialog && (
            <MoveObjectDialog
              itemPath={selectedItemPath}
              getRepositories={this.callApiGetRepositories}
              onClose={this.handleMoveObjectDialog}
            />
          )}

          {isOpenDeleteAlert && (
            <WarningAlert
              confirmButtonText="Delete"
              icon="trash"
              message={
                <span>
                  Are you sure you want to delete <b>{selectedItem}</b>?
                </span>
              }
              onCancel={this.handleDeleteAlert}
              onConfirm={this.handleDeleteItem}
            />
          )}
        </ReduxBlockUi>
      </Container>
    );
  }
}

QueryManager.propTypes = {
  blockUi: PropTypes.object.isRequired,
  openQuery: PropTypes.func.isRequired,
  requestStart: PropTypes.func.isRequired,
  requestSuccess: PropTypes.func.isRequired,
  requestFailure: PropTypes.func.isRequired
};

const mapStateToProps = state => ({
  blockUi: state.blockUi
});

const mapDispatchToProps = dispatch => ({
  openQuery: params => dispatch(actionCreators.openQuery(params)),
  requestStart: message => dispatch(actionCreators.requestStart(message)),
  requestSuccess: () => dispatch(actionCreators.requestSuccess()),
  requestFailure: () => dispatch(actionCreators.requestFailure())
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(withRouter(QueryManager));
